/*===========================================================================
  Copyright (C) 2011-2014 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.lib.xliff2.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

/**
 * Represents a set of {@link ExtAttribute} objects and associated namespaces.
 */
public class ExtAttributes implements Iterable<ExtAttribute> {

	private ArrayList<ExtAttribute> attrs;
	private Map<String, String> namespaces;
	private int autoPrefixCount = 1;

	/**
	 * Creates a new empty {@link ExtAttributes} object.
	 */
	public ExtAttributes () {
		// Argument-less constructor
	}
	
	/**
	 * Copy constructor.
	 * @param original the original object to duplicate.
	 */
	public ExtAttributes (ExtAttributes original) {
		for ( ExtAttribute attr : original ) {
			setAttribute(new ExtAttribute(attr));
		}
		if ( original.hasNamespace() ) {
			for ( String key : original.getNamespaces() ) {
				setNamespace(original.getNamespacePrefix(key), key);
			}
		}
		autoPrefixCount = original.autoPrefixCount;
	}
	
	/**
	 * Gets the value for a given attribute name of a given namespace.
	 * @param namespaceURI the namespace URI of the attribute.
	 * @param localName the name of the attribute.
	 * @return the value (can be null), or null if not found.
	 */
	public String getAttributeValue (String namespaceURI,
		String localName)
	{
		if ( attrs == null ) return null;
		for ( ExtAttribute att : attrs ) {
			if ( att.getLocalPart().equals(localName)
				&& att.getNamespaceURI().equals(namespaceURI) ) {
				return att.getValue();
			}
		}
		return null;
	}
	
	/**
	 * Gets an attribute if it exists.
	 * @param namespaceURI the namespace URI of the attribute QName.
	 * @param localName the local name of the attribute.
	 * @return the attribute or null.
	 */
	public ExtAttribute getAttribute (String namespaceURI,
		String localName)
	{
		if ( attrs == null ) return null;
		for ( ExtAttribute att : attrs ) {
			if ( att.getLocalPart().equals(localName)
				&& att.getNamespaceURI().equals(namespaceURI) ) {
				return att;
			}
		}
		return null;
	}
	
	/**
	 * Sets a given attribute in this set.
	 * If an attribute with the same name and namespace URI already exists, the value of the
	 * existing one is replaced by the new one, but the attribute object itself is not changed.
	 * @param attribute the attribute to add/set.
	 * @return the set/added attribute (may be the attribute passed, or the existing one).
	 */
	public ExtAttribute setAttribute (ExtAttribute attribute) {
		if ( attrs == null ) {
			attrs = new ArrayList<>(2);
		}
		ensureNamespaceAndPrefix(attribute.getNamespaceURI(), attribute.getPrefix());
		int pos = 0;
		for ( ExtAttribute att : attrs ) {
			if ( att.getLocalPart().equals(attribute.getLocalPart())
				&& att.getNamespaceURI().equals(attribute.getNamespaceURI()) ) {
				attrs.set(pos, attribute);
				return attribute;
			}
			pos++;
		}
		attrs.add(attribute);
		return attribute;
	}

	/**
	 * Sets an attribute in this set. 
	 * If an attribute with the same name and namespace URI already exists, the value of the
	 * existing one is replaced by the new one, but the attribute object itself is not changed.
	 * @param namespaceURI the namespace URI of the attribute.
	 * @param localName the name of the attribute.
	 * @param value the value of the attribute.
	 * @return the set/added attribute.
	 */
	public ExtAttribute setAttribute (String namespaceURI,
		String localName,
		String value)
	{
		if ( attrs == null ) {
			attrs = new ArrayList<>(2);
		}
		ensureNamespaceAndPrefix(namespaceURI, null);
		ExtAttribute att = getAttribute(namespaceURI, localName);
		if ( att == null ) {
			att = new ExtAttribute(new QName(namespaceURI, localName, getNamespacePrefix(namespaceURI)), value);
			attrs.add(att);
		}
		att.setValue(value);
		return att;
	}

	/**
	 * Sets an attribute without namespace for this set.
	 * (this means the attribute will be part of the namespace 
	 * of the element where it is defined).
	 * @param localName the name of the attribute.
	 * @param value the value of the attribute.
	 * @return the new attribute.
	 */
	public ExtAttribute setAttribute (String localName,
		String value)
	{
		return setAttribute("", localName, value);
	}
	
	/**
	 * Makes sure there is a prefix associated with each namespace.
	 * If the given URI has no associated prefix, one is create automatically for it.
	 * @param namespaceURI the namespace URI to check.
	 * @param prefix the prefix of the namespace (or null).
	 */
	private void ensureNamespaceAndPrefix (String namespaceURI,
		String oriPrefix)
	{
		if ( getNamespacePrefix(namespaceURI) == null ) {
			if ( namespaces == null ) {
				namespaces = new LinkedHashMap<String, String>();
			}
			String prefix;
			// Try to re-use the prefix first
			if (( oriPrefix != null ) && !namespaces.containsValue(oriPrefix) ) {
				prefix = oriPrefix;
			}
			else {
				while ( true ) {
					prefix = "x"+autoPrefixCount;
					if ( namespaces.containsValue(prefix) ) autoPrefixCount++;
					else break;
				}
			}
			setNamespace(prefix, namespaceURI);
		}
	}

	/**
	 * Removes an attribute from this set.
	 * If the attribute is not found, nothing is done.
	 * @param namespaceURI the namespace URI of the attribute.
	 * @param localName the name of the attribute.
	 */
	public void deleteAttribute (String namespaceURI,
		String localName)
	{
		if ( attrs == null ) return;
		ExtAttribute att = getAttribute(namespaceURI, localName);
		if ( att == null ) return;
		attrs.remove(att);
	}

	/**
	 * Indicates if this set is empty or not.
	 * @return true if this set is empty, false if not.
	 */
	public boolean isEmpty () {
		if (( attrs != null ) && ( attrs.size() > 0 )) return false;
		if (( namespaces != null ) && ( namespaces.size() > 0 )) return false;
		return true;
	}
	
	/**
	 * Gets the number of attributes in this set.
	 * @return the number of attributes in this set.
	 */
	public int size () {
		if ( attrs == null ) return 0;
		return attrs.size();
	}

	/**
	 * Creates an iterator for the attributes in this set.
	 * @return a new iterator for the attributes in this set.
	 */
	@Override
	public Iterator<ExtAttribute> iterator () {
		if ( attrs == null ) {
			attrs = new ArrayList<>(2);
		}
		return attrs.iterator();
	}

	/**
	 * Sets a namespace in this set.
	 * @param prefix the namespace prefix.
	 * @param namespaceURI the namsepace URI.
	 */
	public void setNamespace (String prefix,
		String namespaceURI)
	{
		if ( namespaces == null ) {
			namespaces = new LinkedHashMap<String, String>();
		}
		namespaces.put(namespaceURI, prefix);
	}

	/**
	 * Gets the prefix for a given namespace URI.
	 * @param namespaceURI the namespace URI.
	 * @return the prefix for the given namespace URI, or null if not found.
	 */
	public String getNamespacePrefix (String namespaceURI) {
		if ( namespaces == null ) return null;
		return namespaces.get(namespaceURI);
	}
	
	/**
	 * Indicates if this set has at least one namespace defined.
	 * @return true if this set has at least one namespace defined, false otherwise.
	 */
	public boolean hasNamespace () {
		if ( namespaces == null ) return false;
		return (namespaces.size() > 0);
	}

	/**
	 * Gets the set of keys for the namespaces in this object.
	 * @return the set of keys for the namespaces in this object.
	 */
	public Set<String> getNamespaces () {
		if ( namespaces == null ) return Collections.emptySet();
		return namespaces.keySet();
	}

}
